#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#include "serial-port.h"

#define TIMEOUT_SEC(buflen,baud) (buflen*20/baud+2)
#define TIMEOUT_USEC 0

#define _SERIAL_DEBUG(format, args...)


static int _BAUDRATE (int baudrate)
{
/* reverse baudrate */
    switch (baudrate){
    case B0:
        return (0);
    case B50:
        return (50);
    case B75:
        return (75);
    case B110:
        return (110);
    case B134:
        return (134);
    case B150:
        return (150);
    case B200:
        return (200);
    case B300:
        return (300);
    case B600:
        return (600);
    case B1200:
        return (1200);
    case B2400:
        return (2400);
    case B9600:
        return (9600);
    case B19200:
        return (19200);
    case B38400:
        return (38400);
    case B57600:
        return (57600);
    case B115200:
        return (115200);
    default:
        return (9600);
    }
}

static int BAUDRATE(int baudrate)
{
    switch (baudrate){
    case 0:
        return (B0);
    case 50:
        return (B50);
    case 75:
        return (B75);
    case 110:
        return (B110);
    case 134:
        return (B134);
    case 150:
        return (B150);
    case 200:
        return (B200);
    case 300:
        return (B300);
    case 600:
        return (B600);
    case 1200:
        return (B1200);
    case 2400:
        return (B2400);
    case 9600:
        return (B9600);
    case 19200:
        return (B19200);
    case 38400:
        return (B38400);
    case 57600:
        return (B57600);
    case 115200:
        return (B115200);
    default:
        return (B9600);
    }
}

static int get_baudrate (struct termios *ptermios)
{
    return (_BAUDRATE (cfgetospeed (ptermios)));
}

static void set_baudrate (struct termios *ptermios,int baudrate)
{
    ptermios->c_cflag = BAUDRATE (baudrate);  /* set baudrate */
}

static void set_databit (struct termios *ptermios,int databit)
{
    ptermios->c_cflag &= ~CSIZE;
    switch (databit) {
    case 8:
        ptermios->c_cflag |= CS8;
        break;
    case 7:
        ptermios->c_cflag |= CS7;
        break;
    case 6:
        ptermios->c_cflag |= CS6;
        break;
    case 5:
        ptermios->c_cflag |= CS5;
        break;
    default:
        ptermios->c_cflag |= CS8;
        break;
    }
}

static void set_stopbit (struct termios *ptermios,const char *stopbit)
{
    if (0 == strcmp (stopbit, "1")){
        ptermios->c_cflag &= ~CSTOPB; /* 1 stop bit */
    }else if (0 == strcmp (stopbit, "1.5")){
        ptermios->c_cflag &= ~CSTOPB; /* 1.5 stop bits */
    }else if (0 == strcmp (stopbit, "2")){
        ptermios->c_cflag |= CSTOPB;  /* 2 stop bits */
    }else{
        ptermios->c_cflag &= ~CSTOPB; /* 1 stop bit */
    }
}


static void set_parity_check (struct termios *ptermios,char parity)
{
    switch (parity){
    case 'N':                  /* no parity check */
        ptermios->c_cflag &= ~PARENB;
        break;
    case 'E':                  /* even */
        ptermios->c_cflag |= PARENB;
        ptermios->c_cflag &= ~PARODD;
        break;
    case 'O':                  /* odd */
        ptermios->c_cflag |= PARENB;
        ptermios->c_cflag |= ~PARODD;
        break;
    default:                   /* no parity check */
        ptermios->c_cflag &= ~PARENB;
        break;
    }
}
static int set_port_attr (int fd,int baudrate,int databit, const char *stopbit, char parity,struct termios *ptermios)
{
    bzero (ptermios, sizeof (struct termios));
    cfmakeraw (ptermios);
    set_baudrate (ptermios,baudrate);
    ptermios->c_cflag |= CLOCAL | CREAD;      /* | CRTSCTS */
    set_databit (ptermios,databit);
    set_parity_check (ptermios,parity);
    set_stopbit (ptermios,stopbit);
    ptermios->c_oflag = 0;
    ptermios->c_lflag |= 0;
    ptermios->c_oflag &= ~OPOST;
    ptermios->c_cc[VTIME] = 1;        /* unit: 1/10 second. */
    ptermios->c_cc[VMIN] = 1; /* minimal characters for reading */
    tcflush (fd, TCIFLUSH);
    return (tcsetattr (fd, TCSANOW, ptermios));
}


int sync_read_serial(struct serial_t *dev,void *data, int data_len,uint32_t time)
{
    int retval = 0;
	fd_set fs_read; 
	struct timeval tv_timeout;

    FD_ZERO (&fs_read);
    FD_SET (dev->fd, &fs_read);
    tv_timeout.tv_sec = time;
    tv_timeout.tv_usec = TIMEOUT_USEC;
    retval = select (dev->fd+1, &fs_read, NULL, NULL, &tv_timeout);
    if (retval <= 0){
        return (retval);
    }
	return read (dev->fd, data, data_len);
}

int write_serial(struct serial_t *dev,unsigned char * data, int data_len)
{
    int retval, len = 0, total_len = 0;
	fd_set fs_write;
	struct timeval tv_timeout;
    FD_ZERO (&fs_write);
    FD_SET (dev->fd, &fs_write);
    tv_timeout.tv_sec = TIMEOUT_SEC (data_len, get_baudrate(&dev->termios));
    tv_timeout.tv_usec = TIMEOUT_USEC;
    for (total_len = 0, len = 0; total_len < data_len;){
        retval = select (dev->fd + 1, NULL, &fs_write, NULL, &tv_timeout);
        if (retval){
            len = write (dev->fd, &data[total_len], data_len - total_len);
            if (len < 0){
                return len;
            }else{	
				total_len += len;
			}
        }else{
            tcflush (dev->fd, TCOFLUSH);     /* flush all output data */
            break;
        }
    }
    return (total_len);
}



static void *async_recv_handle(void *arg)
{
    int nread,i=0;
    unsigned char *buf = NULL;
    fd_set fs_read;
	struct timeval tv_timeout;
	struct serial_t *dev = (struct serial_t *)arg;
	
	for(i=0;i<3;i++){
		buf = malloc(SERIAL_ASYNC_RECV_BUFF_SIZE);
		if(buf != NULL){
			break;
		}
		usleep(1000);
	}
	if(buf==NULL){
		_SERIAL_DEBUG("%s buf malloc fail.\n",__func__);
		return NULL;
	}
    while(1)
   	{	
		FD_ZERO (&fs_read);
		FD_SET (dev->fd, &fs_read);
		tv_timeout.tv_sec = TIMEOUT_SEC (SERIAL_ASYNC_RECV_BUFF_SIZE, dev->baudrate);
		tv_timeout.tv_usec = TIMEOUT_USEC;
		if (select (dev->fd+1, &fs_read,NULL,NULL, &tv_timeout) >=0)
		{
			nread = read (dev->fd, buf, SERIAL_ASYNC_RECV_BUFF_SIZE);
			if((nread > 0)&&(dev->async_recv_callback != NULL)){
				dev->async_recv_callback(dev,buf,nread);
			}
		}
    }
	free(buf);
	return NULL;
}

int serial_device_init(struct serial_t *dev,char *tty,uint32_t baudrate,void (*async_callback)(struct serial_t *,void *,uint32_t))
{
	int retval=0;
	struct termios termios_old;
    pthread_t ptid;
    pthread_attr_t  attr;

	if(dev == NULL){
		_SERIAL_DEBUG("dev is null!\n");
		return -1;
	}
	dev->fd = -1;
	dev->tid = 0;
	dev->tty = tty;
	dev->baudrate = baudrate;
	dev->async_recv_callback = async_callback;
	if((access(dev->tty,F_OK)) < 0){
		_SERIAL_DEBUG("%s open fail!\n",dev->tty);
		perror("serial port err");
		retval = -1;
		goto err;
	}
	dev->fd = open(dev->tty,O_RDWR|O_NOCTTY|O_NONBLOCK);
	if(dev->fd <0){
		_SERIAL_DEBUG("%s open fail!\n",dev->tty);
		perror("serial port err");
		retval = -1;
		goto err;
	}
	tcgetattr(dev->fd, &termios_old);
	set_port_attr(dev->fd,dev->baudrate,8, "1",'N',&dev->termios);
	if(dev->async_recv_callback != NULL){
		//创建一个新线程进行接收！
		pthread_attr_init(&attr); 
		pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);
		pthread_attr_setstacksize(&attr, 1024*1024);
		pthread_attr_setschedpolicy(&attr,SCHED_OTHER );  //SCHED_OTHER   SCHED_FIFO
//	  	arm.sched_priority = 70; 
//		pthread_attr_setschedparam(&attr, &parm);
		pthread_create(&ptid, &attr, async_recv_handle,dev);
		pthread_attr_destroy(&attr);
		dev->tid = ptid;
 	}
	return retval;
err:
	if(dev->fd > 0){
		close(dev->fd);
	}
	return retval;
}

int serial_device_deinit(struct serial_t *dev)
{
	if(dev->tid >0){
		pthread_cancel(dev->tid);
		pthread_join(dev->tid,NULL);
	}
	if(dev->fd>0){
		close(dev->fd);
	}
	return 0;
}


